﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Data.Interfaces;
using Data.MyDbContext;
using Utilities;

namespace DAL.Repositories
{
    public class RepositoryPublic<B>
        where B : class, IEntityBase, new()
    {

        /// <summary>
        /// This needs to set in the constructor before any of the internal repository are created
        /// </summary>
        protected DbContext DB { get; set; }

        //store the internal repository -> Repository<T>
        //private RepositoryInternal<B> _internalRepository = null;
        //protected RepositoryInternal<B> InternalRepository {
        //    get 
        //    {
        //        if (_internalRepository == null)
        //        {
        //            _internalRepository = new RepositoryInternal<B>(DB);
        //        }
        //        return _internalRepository;
        //    }
        //}

        public RepositoryPublic() { }

        public RepositoryPublic(DbContextFactory factory)
        {
            DB = factory.DB;
        }

        public RepositoryPublic(DbContext db)
        {
            DB = db;
        }

        public virtual bool Add(B obj)
        {
            if (obj == null) throw new ArgumentNullException("Object to be added to DB cannot be null...");

            DB.Set<B>().Add(obj);
            
            return (DB.SaveChanges() > 0) ? true : false;
        }

        /// <summary>
        /// Update an entity object
        /// </summary>
        /// <param name="obj">object that is being updated</param>
        /// <exception cref="ArgumentNullException">Object passed to function to be Updated cannot be null...</exception>
        /// <returns>return true if update was successful or false otherwise</returns>
        public virtual bool Update(B obj)
        {
            if (obj == null) throw new ArgumentNullException("Object to be Updated cannot be null...");

            DB.Entry(obj).State = EntityState.Modified;
            //DB.Entry(obj).Property("CreatedOn").IsModified = false;

            return (DB.SaveChanges() > 0) ? true : false;
        }

        public bool Delete(Guid id)
        {
            if (!id.IsValid()) throw new InvalidGuidException("Id of the Object to be Deleted is not valid... id=" + id.ToString());

            var obj = DB.Set<B>().Find(id);

            DB.Set<B>().Remove(obj);

            return (DB.SaveChanges() > 0) ? true : false;
        }

        public bool Delete(List<Guid> ids)
        {
            if (!ids.me_IsValid()) throw new ArgumentNullException("ids to be Deleted cannot be null...");

            return Delete(DB.Set<B>().Where(item => ids.Contains(item.Id)).ToList());

        }

        public bool Delete(B obj)
        {
            if (obj == null) throw new ArgumentNullException("Object to be Deleted cannot be null...");

            DB.Set<B>().Remove(obj);

            return (DB.SaveChanges() > 0) ? true : false;
        }

        public bool Delete(List<B> list)
        {
            if (!list.me_IsValid()) throw new ArgumentNullException("list to be Deleted cannot be null...");

            list.ForEach(item => DB.Set<B>().Remove(item));

            return (DB.SaveChanges() > 0) ? true : false;
        }

        public bool Exists(Guid id)
        {
            if (!id.IsValid()) throw new InvalidGuidException("Id is not valid... id=" + id.ToString());

            try
            {
                return DB.Set<B>().Any(item => item.Id == id);
            }
            catch (ArgumentNullException)
            { }
            return false;
        }

        public bool Exists(Expression<Func<B, bool>> predicate)
        {
            if (predicate == null) throw new NullFuncException();

            try
            {
                return DB.Set<B>().Any(predicate);
            }
            catch (ArgumentNullException)
            { }
            return false;
        }

        /// <summary>
        /// Get the first item
        /// </summary>
        /// <returns>returns the first object or null no match occured</returns>
        public B First()
        {
            try
            {
                return DB.Set<B>().First();
            }
            catch (ArgumentNullException) { }
            catch (InvalidOperationException) { }

            //no match was found
            return null;
        }

        /// <summary>
        /// Get the first item that matchs the condition in the predicate 
        /// </summary>
        /// <param name="predicate">condition to find the first element</param>
        /// <exception cref="NullFuncException">The condition cannot be null</exception>
        /// <returns>returns the first object that matches the condition or null no match occured</returns>
        public B First(Expression<Func<B, bool>> predicate)
        {
            if (predicate == null) throw new NullFuncException();

            try
            {
                return DB.Set<B>().First(predicate);
            }
            catch (ArgumentNullException) { }
            catch (InvalidOperationException) { }

            //no match was found
            return null;
        }

        /// <summary>
        /// Get the first item that matchs the condition in the predicate 
        /// </summary>
        /// <param name="predicate">condition to find the first element</param>
        /// <exception cref="NullFuncException">The condition cannot be null</exception>
        /// <returns>returns the first object that matches the condition or null no match occured</returns>
        public B First<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder)
        {
            if (predicate == null) throw new NullFuncException();
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");
            try
            {
                List<B> list = GetAll(predicate, predicateOrder, 1);
                if (list.me_IsValid())
                {
                    return list[0];
                }
            }
            catch (InvalidOperationException) { }

            //no match was found
            return null;
        }


        /// <summary>
        /// Get the object from the database/memory using the guid id of that object
        /// </summary>
        /// <param name="id">primary key of that object</param>
        /// <exception cref="InvalidGuidException">the id is not valid</exception>
        /// <returns>returns the object if found or null otherwise</returns>
        public B Find(Guid id)
        {
            if (!id.IsValid()) throw new InvalidGuidException("Id is not valid... id=" + id.ToString());

            return DB.Set<B>().Find(id);
        }

        /// <summary>
        /// Geta parameter from a object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id">id of the object</param>
        /// <param name="predicate">item => item.Name  ... this will obtain the value of the property Name</param>
        /// <returns>returns the value requested if something is found or null</returns>
        public T GetParam<T>(Guid id, Expression<Func<B, T>> predicate) where T : class
        {
            if (predicate == null) throw new NullFuncException();
            if (!id.IsValid()) throw new InvalidGuidException("Id is not valid... id=" + id.ToString());

            try
            {
                var result = DB.Set<B>().Where(item => item.Id == id).Take(1).Select(predicate).ToList();
                if (result.me_IsValid())
                {
                    return result[0];
                }
            }
            catch (ArgumentNullException) { }
            catch (InvalidOperationException) { }

            //no match was found
            return null;
        }

        public int Count(Expression<Func<B, bool>> predicate)
        {
            if (predicate == null) throw new NullFuncException();

            try
            {
                return DB.Set<B>().Count(predicate);

            }
            catch (ArgumentNullException) { }

            //no match was found
            return 0;
        }

        /// <summary>
        /// Get all the entries of an entity
        /// </summary>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll()
        {
            try
            {
                return DB.Set<B>().ToList();
            }
            //this exception in this case is because the database is empty.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<T> GetAllByParam<T>(Expression<Func<B, T>> predicate) where T : class
        {
            if (predicate == null) throw new NullFuncException();

            try
            {
                return DB.Set<B>().Select(predicate).ToList();
            }
            catch (ArgumentNullException) { }

            //no match was found
            return null;
        }

        /// <summary>
        /// Get the top n entries of an entity
        /// </summary>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll(int top)
        {
            if (top <= 0) throw new InvalidIntegerException("top cannot be less or equal to 0.");
            try
            {
                return DB.Set<B>().Take(top).ToList();
            }
            //this exception in this case is because the database is empty.
            catch (ArgumentNullException) { }
            return null;
        }

        /// <summary>
        /// Get all the entries of an entity that match a criteria
        /// </summary>
        /// <exception cref="NullFuncException">thrown if the predicate is null</exception>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll(Expression<Func<B, bool>> predicate)
        {
            if (predicate == null) throw new NullFuncException();
            try
            {
                return DB.Set<B>().Where(predicate).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        /// <summary>
        /// Get the top n entries of an entity that match a criteria
        /// </summary>
        /// <exception cref="NullFuncException">thrown if the predicate is null</exception>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll(Expression<Func<B, bool>> predicate, int top)
        {
            if (predicate == null) throw new NullFuncException();
            if (top <= 0) throw new InvalidIntegerException("top cannot be less or equal to 0.");

            try
            {
                return DB.Set<B>().Where(predicate).Take(top).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        /// <summary>
        /// Get a specified number of entries of an entity that start at a specified position and that match a criteria 
        /// </summary>
        /// <exception cref="NullFuncException">thrown if the predicate is null</exception>
        /// <exception cref="InvalidIntegerException">thrown if startIndex is less then 0</exception>
        /// <exception cref="InvalidIntegerException">thrown if rowNumber is less or equal to 0</exception>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll(Expression<Func<B, bool>> predicate, int startIndex, int rowNumber)
        {
            if (predicate == null) throw new NullFuncException();
            if (startIndex < 0) throw new InvalidIntegerException("startIndex cannot be less then 0.");
            if (rowNumber <= 0) throw new InvalidIntegerException("rowNumber cannot be less or equal to 0.");

            try
            {
                return DB.Set<B>().Where(predicate).Skip(startIndex).Take(rowNumber).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the (skip , take and toList) because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        /// <summary>
        /// Get a specified number of entries of an entity that start at a specified position
        /// </summary>
        /// <exception cref="InvalidIntegerException">thrown if startIndex is less then 0</exception>
        /// <exception cref="InvalidIntegerException">thrown if rowNumber is less or equal to 0</exception>
        /// <returns>returns an IEnumerable of type TEntity</returns>
        public List<B> GetAll(int startIndex, int rowNumber)
        {
            if (startIndex < 0) throw new InvalidIntegerException("startIndex cannot be less then 0.");
            if (rowNumber <= 0) throw new InvalidIntegerException("rowNumber cannot be less or equal to 0.");
            try
            {
                return DB.Set<B>().Skip(startIndex).Take(rowNumber).ToList();
            }
            //this exception in this case is because the database is empty.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAll<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder)
        {
            if (predicate == null) throw new NullFuncException("the predicate cannot be null...");
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");

            try
            {
                return DB.Set<B>().Where(predicate).OrderBy(predicateOrder).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAll<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder, int top)
        {
            if (predicate == null) throw new NullFuncException("the predicate cannot be null...");
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");
            if (top <= 0) throw new InvalidIntegerException("top cannot be less or equal to 0...");

            try
            {
                return DB.Set<B>().Where(predicate).OrderBy(predicateOrder).Take(top).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAllOrderBy<T>(Expression<Func<B, T>> predicateOrder)
        {
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");

            try
            {
                return DB.Set<B>().OrderBy(predicateOrder).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAllOrderByDesc<T>(Expression<Func<B, T>> predicateOrder)
        {
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");

            try
            {
                return DB.Set<B>().OrderByDescending(predicateOrder).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAllOrderByDesc<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder)
        {
            if (predicate == null) throw new NullFuncException("the predicate cannot be null...");
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");

            try
            {
                return DB.Set<B>().Where(predicate).OrderByDescending(predicateOrder).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAllOrderByDesc<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder, int top)
        {
            if (predicate == null) throw new NullFuncException("the predicate cannot be null...");
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");
            if (top <= 0) throw new InvalidIntegerException("top cannot be less or equal to 0...");

            try
            {
                return DB.Set<B>().Where(predicate).OrderByDescending(predicateOrder).Take(top).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public List<B> GetAllOrderByDesc<T>(Expression<Func<B, bool>> predicate, Expression<Func<B, T>> predicateOrder, int startIndex, int rowNumber)
        {
            if (predicate == null) throw new NullFuncException("the predicate cannot be null...");
            if (predicateOrder == null) throw new NullFuncException("the predicateOrder cannot be null...");
            if (startIndex < 0) throw new InvalidIntegerException("startIndex cannot be less then 0.");
            if (rowNumber <= 0) throw new InvalidIntegerException("rowNumber cannot be less or equal to 0.");

            try
            {
                return DB.Set<B>().Where(predicate).OrderByDescending(predicateOrder).Skip(startIndex).Take(rowNumber).ToList();
            }
            //this exceptionin this case is because the database is empty or because we cannot create the toList because no row passed the where clause.
            catch (ArgumentNullException) { }
            return null;
        }

        public virtual void Dispose()
        {
            if (DB != null)
            {
                DB.Dispose();
            }
        }

    }
}
